Yao Lirong's Blog

P2678 跳石头

2019/06/03

题目来源:洛谷P2678 跳石头


这是一道标准的 “最大值最小”或“最小值最大“ 的题,遇到这种题,我们就可以使用 贪心+二分查找 的方法来做

二分答案/二分查找

有序(单调)的,有界的就可以用二分法查找。

  1. 有界:对于本题,我们可以发现,这个所谓的最短跳跃距离显然不能超过一个范围(跳一次从头跳到尾)。也就是说,答案是有一个确定的范围限制的(开头到结尾的距离内),我们就可以考虑一种另外的方法去解决——枚举答案,并去验证答案是否可行,这实际上是一种倒推

  2. 二分:那么如何确保我们可以最快的找到答案呢?二分是最好选择

  3. 单调:二分的前提条件是什么?是答案区间是整体有序的。我们只考虑合法解,并称之为可行解。考虑所有可行解,我们肯定是要从这些可行解中找到一个最好的作为我们的答案, 这个答案我们称之为最优解。最优解一定可行,但可行解不一定最优。

    • 我们假设整个序列具有单调性,且一个数x为可行解,那么一般的,所有的x’(x’<x)都是可行解。

    • 并且,如果有一个数y是非法解,那么一般的,所有的y’(y’>y)都是非法解。

总结来说,可以使用二分查找的条件:解的上下界确定(l=0,r=L),可以写出判断条件(f(x)<=m),解具有区间单调性(在某个值之前条件都成立,之后都不成立)

本题和 P2855 River Hopscotch 是同一道题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int rocks[50003],ending,num,removed,result;
void finding(int,int);
int main()
{
cin>>ending>>num>>removed;
for(int i=1;i<=num;i++) cin>>rocks[i];
rocks[0]=0; rocks[num+1]=ending;
sort(rocks+1,rocks+num+1);

finding(0,ending);

cout<<result;
return 0;
}
void finding(int m, int n)
{
int mid=(m+n)/2, removing=0;
int now=0,pointer=0;///now 表示我们现在所在的位置,pointer 表示下一个可以跳到的位置
while(pointer<num){///人家这个方法直接一步子迈过去了,根本不需要记录哪个石头被拿掉了,或者判定一个原本有石头的地方被没被拿掉,毕竟题目本身就叫跳石头,为什么要一个个石头看呢,直接跳不就好了
pointer++;
if(rocks[pointer]-rocks[now]<mid)///我们认为mid是最短跳跃距离,如果有某种情况使得跳跃距离比这个最短的还短,我们就需要拿走这块石头来增大这个地方的跳跃距离,使其大于最短跳跃距离
removing++;
else//如果比最短距离长的话,我们就可以跳过去
now=pointer;
}
if(m<=n){
///这个地方我写 m<n 或者 m<=n 有区别吗?我的m到最后的时候只能通过mid+1这一种方式更新,+1又不影响/2以后mid的值,所以这两个判定不是一样的吗?
///确实判定的时候没什么区别,最后都会更新到m=4 n=4 mid=4,但是 m<n 运行到 m=4 n=4 mid=4 会发现 m!<n 所以不会更新 result
if(removing>removed) finding(m,mid-1);
///如果我们以mid为最小距离的情况下移动的石块比我们本应该移动的石块多的话,说明这个答案是不合法的,并且所有大于mid的都不合法(越大于mid移动的石块只会越来越多),所以减少 最小移动距离 使得我们不要移动那么多石块
else if(removing<=removed) { result=mid; finding(mid+1,n);}
///如果以mid为最小距离的情况下移动的石块比我们本应该移动的石块多的话,说明这个答案合法,但是因为我们要寻找最大的最小值,所以增大 最小距离 看看有没有更优的解
}
}
/*
25 5 2
2
11
14
17
21
*/

二分查找模板

非递归形式的二分查找模板

1
2
3
4
5
6
7
8
9
10
11
12
int l=1,r=ll;/// 1 是答案的最小值,ll是答案的最大值 
while(l<=r) { ///当左右边界重合的时候就是答案,退出循环
int mid=(l+r)>>1,q=check(mid);//“>>1”相当于“/2”
if(check) ///当该距离满足条件的时候
{
///去寻找右半部分,看看还有没有符合条件的更大的值
ll=mid+1;///ll上mid右边,找右半部分
ans=mid;///记录答案(更新中)
}
else
l=mid+1;///若这个值不满足,就找左部分
}

下面是一个二分查找的样例

P1824 Aggressive Cows

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
#include<iostream>
#include<cstdio>
#include<algorithm>
using namespace std;
int stall[100005],cow_num,stall_num,ans;
void finding(int,int);
int main()
{
cin>>stall_num>>cow_num;
for(int i=1;i<=stall_num;i++) cin>>stall[i];
sort(stall+1,stall+stall_num+1);

finding(1,stall[stall_num]);

cout<<ans;
return 0;
}
void finding(int m, int n)//看到一个符合要求的就填进去,最后看填进去的cow和一共有的是多是少
{
int mid=(m+n)/2,now=1,pointer=1,cow_mid=1;///cow_mid 从1开始,如果从0开始实际上算的是间距,n+1才是牛的数量
while(pointer<stall_num){
pointer++;
if(stall[pointer]-stall[now]>=mid){///这里注意是 >= 只要比最短的距离(mid)大,我们就可以放一头奶牛在这里
cow_mid++; now=pointer;}
}

if(m<=n){
if(cow_mid>=cow_num) {ans=mid; finding(mid+1,n);} ///如果这次放的比我们需要放的多,说明我们的最短间距太小了,所以要增大最短间距
else finding(m,mid-1);
}
}
CATALOG
  1. 1. 二分答案/二分查找
  2. 2. 二分查找模板
    1. 2.1. P1824 Aggressive Cows